// =============================================
// FD Domain Warp Feedback (EFFECT)
// Made by Ubik and Claude 2026
// =============================================
// Warps the previous frame with a rotating
// wave field and blends with live video.
// Hue slowly rotates in the feedback loop.
//
// Patching:
//   [Video Source]  -> [Actor A: video in 1]
//   [Actor A: out]  -> [Actor B: video in 1]
//   [Actor B: out]  -> [Actor A: video in 2]  <- feedback delay
//   [Actor A: out]  -> [Projector]
// =============================================

// ISADORA_PLUGIN_DESC("Domain Warp Feedback A - warps previous frame with rotating wave field, blends with live video. Hue rotates in feedback loop. Connect video to in 1, Actor B feedback to in 2.")

// ISADORA_FLOAT_PARAM(warp_strength, wstr, 0.0, 0.3, 0.06, "How strongly the feedback is deformed by the wave field each frame.")
// ISADORA_FLOAT_PARAM(warp_speed, wspd, 0.0, 3.0, 0.5, "How fast the wave field moves. Low = dreamy morph, high = fast turbulence.")
// ISADORA_FLOAT_PARAM(decay, deca, 0.0, 0.99, 0.88, "How long the previous frame persists. High = deep persistence and build-up.")
// ISADORA_FLOAT_PARAM(feedback_mix, fbmx, 0.0, 1.0, 0.5, "0 = pure video input, 1 = pure feedback structure.")
// ISADORA_FLOAT_PARAM(color_shift, clsh, 0.0, 1.0, 0.05, "Hue rotation speed in the feedback loop. 0 = static, 1 = one full hue cycle every 2 seconds.")

uniform float warp_strength;
uniform float warp_speed;
uniform float decay;
uniform float feedback_mix;
uniform float color_shift;
uniform float iTime;
uniform vec3  iResolution;
uniform sampler2D tex0;
uniform sampler2D tex1;

vec3 rgb2hsv(vec3 c)
{
    float cmax  = max(c.r, max(c.g, c.b));
    float cmin  = min(c.r, min(c.g, c.b));
    float delta = cmax - cmin;
    float h = 0.0;
    if (delta > 0.0001)
    {
        if      (cmax == c.r) h = mod((c.g - c.b) / delta, 6.0);
        else if (cmax == c.g) h = (c.b - c.r) / delta + 2.0;
        else                  h = (c.r - c.g) / delta + 4.0;
        h /= 6.0;
        if (h < 0.0) h += 1.0;
    }
    float s = (cmax < 0.0001) ? 0.0 : delta / cmax;
    return vec3(h, s, cmax);
}

vec3 hsv2rgb(vec3 c)
{
    float h = c.x * 6.0;
    float s = c.y;
    float v = c.z;
    float i = floor(h);
    float f = h - i;
    float p = v * (1.0 - s);
    float q = v * (1.0 - s * f);
    float t = v * (1.0 - s * (1.0 - f));
    if      (i < 1.0) return vec3(v, t, p);
    else if (i < 2.0) return vec3(q, v, p);
    else if (i < 3.0) return vec3(p, v, t);
    else if (i < 4.0) return vec3(p, q, v);
    else if (i < 5.0) return vec3(t, p, v);
    else               return vec3(v, p, q);
}

void main()
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;
    float t = iTime * warp_speed;

    // Two-layer wave field for organic, non-repetitive motion
    float wx = sin(uv.y * 4.1 + t) * cos(uv.x * 3.7 - t * 0.8)
             + sin(uv.y * 7.3 - t * 0.6 + 1.2) * 0.5;
    float wy = cos(uv.x * 4.6 + t * 0.9) * sin(uv.y * 3.1 + t * 1.1)
             + cos(uv.x * 6.8 - t * 0.7 + 0.7) * 0.5;

    vec2 warpedUV = clamp(uv + vec2(wx, wy) * warp_strength * (1.0 / 1.5), 0.0, 1.0);

    // Sample previous frame, apply hue rotation driven by iTime.
    // color_shift 0->1 = 0 to 0.5 full hue cycles per second - clearly visible.
    vec4 prevFrame = texture2D(tex1, warpedUV);
    vec3 prevHSV   = rgb2hsv(prevFrame.rgb);
    prevHSV.x      = mod(prevHSV.x + iTime * color_shift * 0.5, 1.0);
    vec3 feedback  = hsv2rgb(prevHSV) * decay;

    // Blend with live video
    vec4 videoInput = texture2D(tex0, uv);
    vec3 result = clamp(
        mix(videoInput.rgb, feedback, feedback_mix)
        + videoInput.rgb * (1.0 - feedback_mix) * 0.3,
        0.0, 1.0
    );

    gl_FragColor = vec4(result, videoInput.a);
}
